/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.kairos.commands 
{
	import flash.display.MovieClip;
	import flash.events.Event;
	
	import com.bourre.collection.HashMap;
	import com.bourre.commands.Command;
	import com.bourre.commands.Delegate;
	import com.bourre.events.NumberEvent;
	import com.bourre.log.PixlibDebug;
	import com.bourre.structures.Range;
	import com.kairos.motion.AdvancedMovieClipRT;	

	/**
	 * <code>FrameControllerRT</code> allow to assign a <code>Command</code> object 
	 * to a <code>MovieClip</code> frame. 
	 * 
	 * <p>When frames are skipped because the time between calls is greater
	 * than the frames duration the <code>FrameControllerRT</code> will simulate
	 * the move of the play head on those frames. Results that all registered
	 * commands continue to be executed even if the frames they register with
	 * are skipped.</p>
	 * 
	 * <p>As all frames are played (virtually if needed), commands which will
	 * modifiy the <code>FrameControllerRT</code> are effective immediatly, even if
	 * they are on virtually played frames.</p>
	 * 
	 * <p> In the example below the framerate of the animation is set to 20fps, the
	 * <code>FrameControllerRT</code> object have a framerate 4 times greater, 
	 * then each calls represent 4 frames (in average).</p>
	 * 
	 * <listing>
	 * var fc : FrameControllerRT = new FrameControllerRT ( mcTarget, 80 );
	 * fc.addStop ( 17 );
	 * fc.addProxy ( 18, _global, trace, "command never called" );
	 * fc.play();
	 * </listing>
	 * 
	 * @author 	Cédric Néhémie
	 */
	public class FrameControllerRT extends AdvancedMovieClipRT
	{	
		private var __aCommands : HashMap;
		private var __eFrameEvent : NumberEvent;
		
		/**
		 * The <code>FrameControllerRT</code> work as the <code>AdvancedMovieClipRT</code>,
		 * so the constructor don't add or remove anything.
		 * 
		 * @param   mc			the MovieClip to work with.    
		 * @param   fps 		the speed of the animation.
		 * @param	loopCount 	number of loops before stop.
		 * @param	range 		a valid <code>Range</code> object
		 * @param	r			<code>true</code> to make the movieclip playing reversed. 
		 */
		public function FrameControllerRT( 	mc : MovieClip, fps : Number = 40, loopCount : Number = 0, range : Range = null, r : Boolean = false ) 
		{
			__aCommands = new HashMap();

			super( mc, fps, loopCount, range, r );
			
		}
		
		/**
		 * Add a command to a specific frame of the animation.
		 * 
		 * <p>If the command don't exist or if the frame can't
		 * be found in the object, the function failed and return
		 * <code>false</code>.</p>
		 * 
		 * <p>If a command allready exist in the HashMap, the function
		 * failed and return <code>false</code>.</p>
		 * 
		 * @param	f	frame index which will trigger the command.
		 * @param	o	<code>Command</code> to execute.
		 * @return	<code>true</code> if the command have been registered,
		 * 			either <code>false</code>
		 */
		public function addCommand ( f : Number, o : Command ) : Boolean
		{
			if( !isValidFrame ( f ) || !isEmpty( f ) || !o )
			{
				PixlibDebug.ERROR( this + ".addCommand() can't add " + o +" at " + f );
				return false;
			}
				
			__aCommands.put( f, o );		
			return true;
		}
		
		/**
		 * Add a function proxy to a specific frame of the animation.
		 * 
		 * <p>If the proxy don't exist or if the frame can't
		 * be found in the object, the function failed and return
		 * <code>false</code>.</p>
		 * 
		 * <p>If a command allready exist in the HashMap, the function
		 * failed and return <code>false</code>.</p>
		 * 
		 * <p>You can pass additionnal arguments to the proxy as
		 * in Pixlib implementation.</p>
		 * 
		 * @param	f	 frame index which will trigger the proxy.
		 * @param	fn	 function to proxy.
		 * @param	args arguments array to pass to the method closure.
		 * @return	<code>true</code> if the proxy have been registered,
		 * 			either <code>false</code>
		 */
		public function addProxy ( f : Number, fn : Function, ... args ) : Boolean
		{
			if( !isValidFrame ( f ) || !isEmpty( f ) || fn == null ) 
			{
				PixlibDebug.ERROR( this + ".addProxy() can't add the proxy at " + f );
				return false;
			}
				
			var d : Delegate = new Delegate ( fn );
			d.setArguments.apply( d, args );
				
			__aCommands.put ( f, d );		
			return true;
		}
		
		/**
		 * Add a simple <code>stop</code> call at the specified frame.
		 * 
		 * <p><code>addStop</code> method is subject at all restrictions
		 * of the <code>addCommand</code> and <code>addProxy</code>.</p>
		 * 
		 * @param	f	frame where the animation stop.
		 * @return	<code>true</code> if the stop have been registered,
		 * 			either <code>false</code>
		 */
		public function addStop ( f : Number ) : Boolean
		{
			if( !isValidFrame ( f ) || !isEmpty( f ) ) 
			{
				PixlibDebug.ERROR( this + ".addStop() can't add a stop at " + f );
				return false;
			}
				
			__aCommands.put ( f, new Delegate( _stop ) );		
			return true;
		}
		
		/**
		 * Remove a single instance of the passed-in command if finded
		 * in the HashMap.
		 * 
		 * <p>If the same command is registered several times, the remove method
		 * will only remove the first instance.</p>
		 * 
		 * <p>The function return <code>true</code> if the command
		 * have been removed, either <code>false</code>.
		 * 
		 * @param	o	<code>Command</code> to remove.
		 * @return	<code>true</code> if the command have been removed,
		 * 			either <code>false</code>
		 */
		public function remove ( o : Command ) : Boolean
		{
			if ( !__aCommands.containsValue( o ) )
			{
				PixlibDebug.WARN( o + " don't exist in " + this );
				return false;
			}
			
			var keys : Array = __aCommands.getKeys();			
			for each( var key : * in keys )
			{
				if( __aCommands.get( key ) == o )
				{
					__aCommands.remove( key );
					break;
				}
			}
			return true;
		}
		
		/**
		 * Remove a <code>Command</code> registered to the specified
		 * frame.
		 * 
		 * <p>If the passed-in frame don't exist in the animation
		 * or if there's no command registered with that frame, 
		 * the function failed and return <code>false</code>.</p>
		 * 
		 * @param	f	frame number in the current <code>MovieClip</code>.
		 * @return	<code>true</code> if a command have been removed,
		 * 			either <code>false</code>
		 */
		public function removeAt ( f : Number ) : Boolean
		{
			if( !isValidFrame ( f ) ) 
				return false;
	
			__aCommands.remove( f );
			return true;
		}
		
		/**
		 * Returns command stored at the specified frame.
		 * 
		 * <p>If the passed-in frame don't exist in the animation
		 * or if there's no command stored at this framethe function
		 * return <code>null</code>.</p>
		 * 
		 * @param	f	frame number in the current <code>MovieClip</code>.
		 * @return	<code>Comand</code> stored at the specified frame or <code>null</code>
		 */
		public function getCommand ( f : Number ) : Command
		{
			if( !isValidFrame ( f ) ) 
				return null;
				
			return __aCommands.get( f );
		}
		
		/**
		 * Returns <code>true</code> if the frame exist in the current
		 * <code>MovieClip</code>.
		 * 
		 * @param	f	Frame index to verify.
		 * @return	<code>true</code> if the frame is valid, either <code>false</code>.
		 */
		public function isValidFrame ( f : Number ) : Boolean
		{
			return ( f > 0 && f <= _mcTarget.totalFrames );
		} 
		
		/**
		 * Returns <code>true</code> if there is no commands stored
		 * at the specified frame.
		 * 
		 * @param	f	frame index to verify.
		 * @return	<code>true</code> if the frame is empty, either <code>false</code>.
		 */
		public function isEmpty ( f : Number ) : Boolean
		{
			return !__aCommands.containsKey( f );
		}
		
		/**
		 * Override the <code>AdvancedMovieClipRT._movePlayHead</code> method.
		 * 
		 * <p>Run virtually all the frames between the current frame
		 * and the next frame according to the current move.</p>
		 * 
		 * <p>Even if there is several frames to play there is only
		 * the last frames which is really displayed.</p>
		 * 
		 * @param	f	<code>Number</code> of frames in the gap
		 */	 
		protected override function _movePlayHead ( f : Number ) : void
		{	
			/*
			 * If the step is greater than 1 frame, we loop
			 * for each frame and trigger command. If a step
			 * stop the animation the loop break.
			 */
		 	if( f > 1 )
			{
				var n : Number = 0;
				while( ++n <= f && _bIsRunning ) super._movePlayHead ( 1 );
			}
			else super._movePlayHead ( f );

		 	super._frameChanged ( _nRangeStart + _nFrame );	
		}
		
		/**
		 * Execute the command associated to the passed-in frame.
		 * 
		 * <p>A <code>NumberEvent</code> containing the current frame
		 * index is passed to the <code>execute</code> function.</p> 
		 * 
		 * @param	f	<code>Number</code> of the frame to handle
		 */
		protected override function _frameChanged ( f : Number ) : void
		{
			if( !isEmpty( f ) )
			{
				__eFrameEvent = new NumberEvent ( "frameChanged", this, f );
				
				( __aCommands.get( f ) as Command ).execute( __eFrameEvent );
			}
		}
		
		/**
		 * 
		 * @param e
		 */
		private function _stop( e : Event ) : void
		{
			stop();
		}
	}
}